/**
 * \file ping_icmp.c
 * \brief ping en ICMP
 * \author Pierre Neumann / Julien Meutelet
 * \version 0.1
 * \date 20 decembre 2013
 *
 * Gère l'envoi de ping en ICMP
 *
 */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <inttypes.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <errno.h>

#include "ping_icmp.h"
#include "packet_icmp.h"
#include "packet_utils.h"

/**
 * \fn ping_icmp(char* ip,int ttl,int timeout)
 * \brief Envoi un ping en ICMP
 *
 * \param ip Chaine de caractère représentant l'adresser IP à pinger.
 * \param ttl Entier utilisé pour initialiser le champ ttl de l'entête IP.
 * \param timeout Entier, timeout avant de considérer un paquet perdu.
 * \return Structure ping_result.
 */ 
struct ping_result* ping_icmp(char* ip,int ttl,int timeout){
	// On déclare les différents timers. 
	struct timeval before, after, tv;
	tv.tv_sec = timeout;
  	tv.tv_usec = 0;
	// On alloue la structure de retour
	struct ping_result* res = malloc(sizeof(struct ping_result));
	if(res == NULL) exit(1);

	// Quelques variables utiles
        int sockfd, optval;

	socklen_t addrlen = sizeof(struct  sockaddr);

	struct sockaddr_in server ;
	struct sockaddr_in all;
	all.sin_family = AF_INET;
	all.sin_addr.s_addr = INADDR_ANY;
	server.sin_family = AF_INET; 
        server.sin_addr.s_addr = inet_addr(ip);

	// Initialisation de la socket raw
        sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
 
	if(sockfd == -1){
		 perror("Erreur creation socket");
      		 exit(1);
    	}    
	
	// Option pour lui dire de ne pas faire sa propre entête IP
        if(setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&optval,sizeof(int)) < 0){
		close(sockfd);
		perror("Erreur setsockopt");
      		exit(1);
	} 
	// Option pour assigner un timeout à la socket
  	if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,&tv,sizeof(tv)) < 0) {
      		perror("Erreur setsockopt Timeout");
  	}
  
	// Paquet à envoyer
	char *packet_ping = NULL;
	struct in_addr my_ip = get_my_ip();
	// On l'initialise
	init_icmp_header(&packet_ping,&(server.sin_addr),&my_ip,ttl);
	  
	// Paquet de retour
	char *packet_pong = (char *) malloc(ICMP_PACKET_SIZE);
	if(packet_pong == NULL || packet_ping == NULL){
		exit(1);
	}

	// Pour mesurer le temps d'allez-retour on récupère le temps avant d'envoyer le paquet
    	gettimeofday (&before, NULL);

	// On envoi le paquet
        if(sendto(sockfd,packet_ping,ICMP_PACKET_SIZE,0,(struct sockaddr*)&server,addrlen) <=0){
		perror("Erreur sendto()");
	}
	
	// Sert à enregistrer le nombre de bits récupéré
	int pong_size = 0;	

	//reception du paquet
      	if((pong_size=recvfrom(sockfd,packet_pong,ICMP_PACKET_SIZE,0,(struct sockaddr *)&all,&addrlen))<=0){
		//Si erreur ou Timeout dépassé
		free(res);
		return NULL;
	}
	
	// On récupère le temps après reception du paquet
	gettimeofday (&after, NULL);

	// On initialise le resultat
	init_ping_result(res,&packet_pong,pong_size,((after.tv_sec-before.tv_sec)*1000000L+after.tv_usec)-before.tv_usec); 

	// On libère la memoire 
        close(sockfd);
        free(packet_ping);
        free(packet_pong);
        return res;
}
